#include <cctype>
#include <cstdio>

#include <iostream>
#include <sstream>

#include "tdop.hpp"

enum
{
	BP_OR  = 10,	// a || b
	BP_AND = 10,	// a && b
	BP_EQ  = 20,	// a == b
	BP_NEQ = 20,	// a != b
	BP_ADD = 30,	// a + b
	BP_SUB = 30,	// a - b
	BP_MUL = 50,	// a * b
	BP_DIV = 50,	// a / b
	BP_NEG = 100	// - a
};

typedef tdop::Token<int> Token;
typedef std::shared_ptr<Token> TokenPtr;

class NumberToken : public Token
{
public:
	static TokenPtr make(int value)
	{
		return TokenPtr(new NumberToken(value));
	}

	int value() const
	{
		return value_;
	}

private:
	NumberToken(int value) : Token(0), value_(value)
	{
	}

private:
	int value_;
};

class PlusToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new PlusToken);
		return token;
	}

private:
	PlusToken() : Token(BP_ADD)
	{
	}
};

class MinusToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new MinusToken);
		return token;
	}

private:
	MinusToken() : Token(BP_SUB)
	{
	}
};

class AsteriskToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new AsteriskToken);
		return token;
	}

private:
	AsteriskToken() : Token(BP_MUL)
	{
	}
};

class SlashToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new SlashToken);
		return token;
	}

private:
	SlashToken() : Token(BP_DIV)
	{
	}
};

class RightParanToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new RightParanToken);
		return token;
	}

private:
	RightParanToken() : Token(0)
	{
	}
};

class LeftParanToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new LeftParanToken);
		return token;
	}

private:
	LeftParanToken() : Token(0)
	{
	}
};

class EqualToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new EqualToken);
		return token;
	}

private:
	EqualToken() : Token(BP_EQ)
	{
	}
};

class NotEqualToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new NotEqualToken);
		return token;
	}

private:
	NotEqualToken() : Token(BP_NEQ)
	{
	}
};

class LogicalAndToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new LogicalAndToken);
		return token;
	}

private:
	LogicalAndToken() : Token(BP_AND)
	{
	}
};

class LogicalOrToken : public Token
{
public:
	static TokenPtr make()
	{
		static TokenPtr token(new LogicalOrToken);
		return token;
	}

private:
	LogicalOrToken() : Token(BP_OR)
	{
	}
};

class ExprParser : public tdop::Parser<int>
{
public:
	ExprParser(std::istream& stream) : stream_(stream)
	{
	}

private:
	std::istream& stream_;

	TokenPtr get_token()
	{
		TokenPtr token;

		if (stream_.good())
		{
			int ch;

			while (true)
			{			
				ch = stream_.get();

				if (!isspace(ch))
					break;
			}

			if (ch == '+')
			{
				token = PlusToken::make();
			}
			else if (ch == '-')
			{
				token = MinusToken::make();
			}
			else if (ch == '*')
			{
				token = AsteriskToken::make();
			}
			else if (ch == '/')
			{
				token = SlashToken::make();
			}
			else if (ch == '(')
			{
				token = LeftParanToken::make();
			}
			else if (ch == ')')
			{
				token = RightParanToken::make();
			}
			else if (ch == '=' || ch == '!')
			{
				if (stream_.get() == '=')
					token = ch == '=' ? EqualToken::make() : NotEqualToken::make();
				else
					stream_.unget();
			}
			else if (ch == '&')
			{
				if (stream_.get() == '&')
					token = LogicalAndToken::make();
				else
					stream_.unget();
			}
			else if (ch == '|')
			{
				if (stream_.get() == '|')
					token = LogicalOrToken::make();
				else
					stream_.unget();
			}
			else if (isdigit(ch))
			{
				int value = ch - '0';

				while (true)
				{
					ch = stream_.get();

					if (!isdigit(ch))
					{
						stream_.unget();
						break;
					}

					value *= 10;
					value += ch - '0';
				}

				token = NumberToken::make(value);
			}

			if (!token && ch != EOF)
			{
				std::string what;

				what += "unexpected char '";
				what += ch;
				what += "'";

				throw tdop::SyntaxError(what.c_str());
			}
		}

		return token;
	}

	int nud(tdop::Token<int> const& token)
	{
		if (typeid(token) == typeid(NumberToken))
		{
			return static_cast<NumberToken const&>(token).value();
		}

		if (typeid(token) == typeid(MinusToken))
		{
			return -expression(BP_NEG);
		}

		throw tdop::SyntaxError("nud not defined");
	}

	int led(int left, tdop::Token<int> const& token)
	{
		if (typeid(token) == typeid(PlusToken))
			return left + expression(token.lbp());

		if (typeid(token) == typeid(MinusToken))
			return left - expression(token.lbp());

		if (typeid(token) == typeid(AsteriskToken))
			return left * expression(token.lbp());

		if (typeid(token) == typeid(AsteriskToken))
			return left / expression(token.lbp());

		if (typeid(token) == typeid(EqualToken))
			return left == expression(token.lbp());

		if (typeid(token) == typeid(NotEqualToken))
			return left != expression(token.lbp());

		if (typeid(token) == typeid(LogicalAndToken))
			return left && expression(token.lbp());

		if (typeid(token) == typeid(LogicalOrToken))
			return left || expression(token.lbp());

		if (typeid(token) == typeid(LeftParanToken))
		{
			int expr = expression(0);

        	match(typeid(RightParanToken));

        	return expr;
		}
		
		throw tdop::SyntaxError("led not defined");
	}
};

int main(int argc, char* argv[])
{
	using namespace std;

	while (true)
	{
		string line;

		cout << ">> ";

		getline(cin, line);

		if (line.empty())
			break;

		istringstream ss(line);

		ExprParser parser(ss);

		cout << " = ";

		try
		{
			cout << parser.parse();
		}
		catch (tdop::SyntaxError& e)
		{
			cout << "SyntaxError: " << e.what();
		}

		cout << '\n';
	}

	return 0;
}
